﻿using System;
using System.Collections.Generic;
using System.Text;
using SilverChart.Core.Service;

namespace SilverChart.Core.Configuration {
    /// <summary>
    /// Configuration Service Point.
    /// 
    /// This interface define the basic functionallity of Configuration Service.
    /// </summary>
    [ServicePoint]
    public interface ConfigurationService {
        /// <summary>
        /// Build configuration from a String.
        /// 
        /// The method return the root configuration item.
        /// This method will iterator all ConfigurationBuilderFactory Service Provider, all check it compatibility with the config string.
        /// The first compatible Provider will be use.
        /// </summary>
        /// <param name="config">Configuration String. It may be JSON, XML...</param>
        /// <returns>The root configuration item</returns>
        ConfigurationItem ParseConfiguration(String config);
        ConfigurationItem ParseConfiguration(String config, String RootItemName);

        /// <summary>
        /// The ROOT configuration builder.
        /// </summary>
        ConfigurationParser RootParser { get; }

        /// <summary>
        /// The ROOT configuration item.
        /// </summary>
        ConfigurationItem Configuration { get; }

        /// <summary>
        /// Add a new Configuration Item provider.
        /// </summary>
        /// <param name="attr">Item class attribute</param>
        /// <param name="item">Item class type</param>
        void AddConfigurationItem(ConfigurationItemAttribute attr, Type item);

        /// <summary>
        /// Return all registed Configuration Item classes which parent class is parent and name is itemName
        /// </summary>
        /// <param name="parent">Parent configuration item</param>
        /// <param name="itemName">configuration item's name, null means all </param>
        /// <returns>All configuration item classes</returns>
        IList<Type> RegisteredItems(ConfigurationItem parent, String itemName);

        /// <summary>
        /// Return ROOT configuration Item classes, which name is rootName
        /// </summary>
        /// <param name="rootName">ROOT configuration item's name</param>
        /// <returns>All ROOT configuration name, which name rootName</returns>
        IList<Type> GetRootConfiguratioItems(String rootName);
    }

    /// <summary>
    /// The default implementation of Configuration Service Point.
    /// </summary>
    [ServiceProvider(LayzeCreation = false, Singleton = true, ServiceName = "Configuration Servcie")]
    internal class ConfigurationServcieImpl : ConfigurationService {
        private IDictionary<Type, IDictionary<String, List<Type>>> configDefinition;
        private Dictionary<String, List<Type>> rootDefinition;
        private IDictionary<ConfigurationItem, ConfigurationParser> builderRegisters;
        private IDictionary<Type, List<Type>> converters;

        public ConfigurationServcieImpl() {
            configDefinition = new Dictionary<Type, IDictionary<String, List<Type>>>();
            rootDefinition = new Dictionary<String, List<Type>>();
            builderRegisters = new Dictionary<ConfigurationItem, ConfigurationParser>();
            converters = new Dictionary<Type, List<Type>>();
        }

        public ConfigurationParser RootParser { get; private set; }

        public ConfigurationItem ParseConfiguration(String config) {
            return ParseConfiguration(config, null);
        }

        public ConfigurationItem ParseConfiguration(String config, String RootItemName) {
            IList<ConfigurationParserFactory> configFactories = ServiceManager.Instance.GetServices<ConfigurationParserFactory>();
            foreach (ConfigurationParserFactory factory in configFactories) {
                if (factory.IsCompatible(config)) {
                    RootParser = factory.CreateParser(config, RootItemName);
                }
            }
            if (RootParser == null) {
                throw new ArgumentException("Cannot parse configuration!");
            }
            return RootParser.Configuration;
        }

        public ConfigurationItem Configuration {
            get {
                return RootParser.Configuration;
            }
        }

        public void AddConfigurationItem(ConfigurationItemAttribute attr, Type item) {
            if (item == null || !typeof(ConfigurationItem).IsAssignableFrom(item)) {
                throw new ArgumentException("name is null or item is not derived from ConfigurationItem");
            }

            if (attr.IsRootItem == true) {
                List<Type> namedRootItems = null;
                if (!rootDefinition.TryGetValue(attr.Name, out namedRootItems)) {
                    namedRootItems = new List<Type>();
                    rootDefinition[attr.Name] = namedRootItems;
                }
                namedRootItems.Add(item);
            }
            else {
                Type parent = attr.Parent;
                if (parent == null) {
                    parent = typeof(ConfigurationRootItem);
                }

                IDictionary<String, List<Type>> childConfigurations = null;
                if (!configDefinition.TryGetValue(parent, out childConfigurations)) {
                    childConfigurations = new Dictionary<String, List<Type>>();
                    configDefinition[parent] = childConfigurations;
                }

                List<Type> specifiedChildConfigurations = null;
                if (!childConfigurations.TryGetValue(attr.Name, out specifiedChildConfigurations)) {
                    specifiedChildConfigurations = new List<Type>();
                    childConfigurations[attr.Name] = specifiedChildConfigurations;
                }

                specifiedChildConfigurations.Add(item);
            }
        }

        public IList<Type> RegisteredItems(ConfigurationItem parent, String itemName) {
            Type parentType = parent is DefaultConfigurationItem ? typeof(ConfigurationRootItem) : parent.GetType();
            IDictionary<String, List<Type>> childConfigurations = null;
            if (configDefinition.TryGetValue(parentType, out childConfigurations)) {
                List<Type> specifiedChildConfigurations = null;
                if (childConfigurations.TryGetValue(itemName, out specifiedChildConfigurations)) {
                    return specifiedChildConfigurations.AsReadOnly();
                }
            }
            return null;
        }

        public IList<Type> GetRootConfiguratioItems(String rootName) {
            rootName = rootName == null || rootName.Trim().Length == 0 ?
                ConfigurationRootItem.DEFAULT_ROOT_ITEM_NAME : rootName;
            if (rootDefinition.ContainsKey(rootName)) {
                return rootDefinition[rootName].AsReadOnly();
            }
            return null;
        }

        //internal void RegisterBuilder(IConfigurationItem item, ConfigurationBuilder builder) {
        //    builderRegisters[item] = builder;
        //}

        //internal ConfigurationBuilder GetBuilder(IConfigurationItem item) {
        //    ConfigurationBuilder builder = null;
        //    builderRegisters.TryGetValue(item, out builder);
        //    return builder;
        //}

        #region override ToString
        public override String ToString() {
            StringBuilder sb = new StringBuilder();
            foreach (Type parent in configDefinition.Keys) {
                sb.Append(parent.FullName);
                sb.Append("\n");
                BuildConfigurationString(sb, "\t", configDefinition[parent]);
            }
            sb.Append("Root Configuration Item:\n");
            BuildConfigurationString(sb, "\t", rootDefinition);
            return sb.ToString();
        }

        private void BuildConfigurationString(StringBuilder sb, String indent, IDictionary<String, List<Type>> map) {
            foreach (String key in map.Keys) {
                sb.Append(indent);
                sb.Append(key);
                sb.Append(":");
                foreach (Type type in map[key]) {
                    sb.Append(type.FullName);
                    sb.Append("; ");
                }
                sb.Append("\n");
            }
        }
        #endregion
    }
}
